﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Sockets;
using System.Diagnostics;
using System.Windows;
using System.Threading.Tasks;

namespace LightingPlatform.TCP
{
    public class TcpChannel
    {
        #region 属性
        private Socket Socket
        {
            get;
            set;
        }

        public TcpChannelState State
        {
            get;
            private set;
        }

        public EndPoint RemoteEndPoint
        {
            get
            {
                return this.Socket == null ? null : this.Socket.RemoteEndPoint;
            }
        }

        public Queue<byte[]> SendQueue
        {
            get;
            private set;
        }
        #endregion

        #region 函数
        public TcpChannel()
        {
            this.State = TcpChannelState.Disconnected;
        }

        #region Connect
        private TaskCompletionSource<bool> ConnectTask
        {
            get;
            set;
        }
        public Task<bool> ConnectAsync(EndPoint remoteEndPoint)
        {
            return this.ConnectAsync(remoteEndPoint, null);
        }

        public async Task<bool> ConnectAsync(EndPoint remoteEndPoint, Queue<byte[]> sendQueue)
        {
            if (!this.Connect(remoteEndPoint, sendQueue))
            {
                return false;
            }

            this.ConnectTask = new TaskCompletionSource<bool>();
            return await this.ConnectTask.Task;
        }

        public bool Connect(EndPoint remoteEndPoint)
        {
            return Connect(remoteEndPoint, null);
        }

        public bool Connect(EndPoint remoteEndPoint, Queue<byte[]> sendQueue)
        {
            if (this.State != TcpChannelState.Disconnected)
            {
                return false;
            }

            this.SendQueue = sendQueue;

            if (this.Socket == null)
            {
                this.Socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                this.SendAsyncEventArgs = new SocketAsyncEventArgs()
                {
                    RemoteEndPoint = remoteEndPoint
                };
                this.SendAsyncEventArgs.Completed += this.ConnectCompleted;
            }

            this.State = TcpChannelState.Connecting;

            if (!this.Socket.ConnectAsync(this.SendAsyncEventArgs))
            {
                this.ConnectCompleted(this.Socket, this.SendAsyncEventArgs);
            }

            return true;
        }

        private void ConnectCompleted(object sender, SocketAsyncEventArgs e)
        {
            e.Completed -= this.ConnectCompleted;

            if (e.SocketError == SocketError.Success)
            {
                this.State = TcpChannelState.Connected;

                if (this.SendQueue == null)
                {
                    this.SendQueue = new Queue<byte[]>();
                }
                this.SendAsyncEventArgs.Completed += this.SendCompleted;

                this.ReceiveAsyncEventArgs = new SocketAsyncEventArgs();
                this.ReceiveAsyncEventArgs.Completed += this.ReceiveCompleted;
                this.ReceiveBuffer = new byte[this.Socket.ReceiveBufferSize];

                this.Receive();

                this.OnConnected();

                this.ConnectTask.SetResult(true);
                this.ConnectTask = null;
            }
            else
            {
                this.State = TcpChannelState.Disconnected;

                this.OnError(e.SocketError);

                this.ConnectTask.SetException(new SocketException((int)e.SocketError));
                this.ConnectTask = null;
            }
        }
        #endregion

        #region Send
        private SocketAsyncEventArgs SendAsyncEventArgs
        {
            get;
            set;
        }

        public bool Send(byte[] buffer)
        {
            if (this.State != TcpChannelState.Connected)
            {
                return false;
            }

            lock (this.SendQueue)
            {
                this.SendQueue.Enqueue(buffer);

                if (this.SendQueue.Count == 1)
                {
                    this.Send();
                }
            }

            return true;
        }

        private void Send()
        {
            byte[] buffer = this.SendQueue.Peek();

            this.SendAsyncEventArgs.SetBuffer(buffer, 0, buffer.Length);

            if (!this.Socket.SendAsync(this.SendAsyncEventArgs))
            {
                this.SendCompleted(this.Socket, this.SendAsyncEventArgs);
            }
        }

        private void SendCompleted(object sender, SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success && e.BytesTransferred > 0)
            {
                if (e.Offset + e.BytesTransferred < e.Count)
                {
                    e.SetBuffer(e.Offset + e.BytesTransferred, e.Count - e.BytesTransferred);

                    if (!this.Socket.SendAsync(e))
                    {
                        this.SendCompleted(sender, e);
                    }
                }
                else
                {
                    lock (this.SendQueue)
                    {
                        this.SendQueue.Dequeue();

                        this.OnSended();

                        if (this.SendQueue.Count > 0)
                        {
                            this.Send();
                        }
                    }
                }
            }
            else
            {
                if (this.State != TcpChannelState.Close)
                {
                    this.Close();

                    this.OnError(e.SocketError);
                }
            }
        }
        #endregion

        #region Receive
        private SocketAsyncEventArgs ReceiveAsyncEventArgs
        {
            get;
            set;
        }

        private byte[] ReceiveBuffer
        {
            get;
            set;
        }

        private void Receive()
        {
            this.ReceiveAsyncEventArgs.SetBuffer(this.ReceiveBuffer, 0, this.ReceiveBuffer.Length);

            if (!this.Socket.ReceiveAsync(this.ReceiveAsyncEventArgs))
            {
                this.ReceiveCompleted(this.Socket, this.ReceiveAsyncEventArgs);
            }
        }

        private void ReceiveCompleted(object sender, SocketAsyncEventArgs e)
        {
            if (e.SocketError == SocketError.Success && e.BytesTransferred > 0)
            {
                this.OnReceived(this.ReceiveBuffer, e.BytesTransferred);

                this.Receive();
            }
            else
            {
                if (this.State != TcpChannelState.Close)
                {
                    this.Close();

                    this.OnError(e.SocketError);
                }
            }
        }
        #endregion

        #region Close
        public bool Close()
        {
            if (this.State != TcpChannelState.Connected)
            {
                return false;
            }

            this.Socket.Shutdown(SocketShutdown.Both);
            this.Socket.Close();

            this.State = TcpChannelState.Close;

            this.OnDisconnectCompleted();

            return true;
        }
        #endregion
        #endregion

        #region 事件
        #region Connected
        public event EventHandler Connected;

        protected virtual void OnConnected()
        {
            if (this.Connected != null)
            {
                this.Connected(this, EventArgs.Empty);
            }
        }
        #endregion

        #region Disconnected
        public event EventHandler Disconnected;

        protected virtual void OnDisconnectCompleted()
        {
            if (this.Disconnected != null)
            {
                this.Disconnected(this, EventArgs.Empty);
            }
        }
        #endregion

        #region Sended
        public event EventHandler Sended;

        protected virtual void OnSended()
        {
            if (this.Sended != null)
            {
                this.Sended(this, EventArgs.Empty);
            }
        }
        #endregion

        #region Received
        public event EventHandler<TcpChannelReceiveEventArgs> Received;

        protected virtual void OnReceived(byte[] buffer, int count)
        {
            if (this.Received != null)
            {
                this.Received(this, new TcpChannelReceiveEventArgs(buffer, count));
            }
        }
        #endregion

        #region Error
        public event EventHandler<TcpChannelErrorEventArgs> Error;

        protected virtual void OnError(SocketError socketError)
        {
            if (this.Error != null)
            {
                this.Error(this, new TcpChannelErrorEventArgs(new SocketException((int)socketError)));
            }
        }
        #endregion
        #endregion
    }

    public enum TcpChannelState
    {
        Disconnected,
        Connecting,
        Connected,
        Close
    }

    #region TcpChannelErrorEventArgs
    public class TcpChannelErrorEventArgs : EventArgs
    {
        public TcpChannelErrorEventArgs(Exception exception)
        {
            this.Exception = exception;
        }

        public Exception Exception
        {
            get;
            private set;
        }
    }
    #endregion

    #region TcpChannelReceiveEventArgs
    public class TcpChannelReceiveEventArgs : EventArgs
    {
        #region 属性
        private byte[] Buffer
        {
            get;
            set;
        }

        public int Count
        {
            get;
            private set;
        }

        public byte[] Data
        {
            get
            {
                byte[] data = new byte[this.Count];

                Array.Copy(this.Buffer, 0, data, 0, this.Count);

                return data;
            }
        }
        #endregion

        #region 函数
        public TcpChannelReceiveEventArgs(byte[] buffer, int count)
        {
            this.Buffer = buffer;
            this.Count = count;
        }

        public void Copy(byte[] destination)
        {
            this.Copy(destination, 0);
        }

        public void Copy(byte[] destination, int destinationIndex)
        {
            Array.Copy(this.Buffer, 0, destination, destinationIndex, this.Count);
        }
        #endregion
    }
    #endregion
}
